﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Logging;

using Microsoft.Cci;

using PartialExpressionCompleter.Query;
using PartialExpressionCompleter.Util;

namespace PartialExpressionCompleter.Experiment
{
    class ComparisonExperiment : ExpressionExperiment<IExpression>
    {
        public ComparisonExperiment(IAssembly unit)
            : base(unit)
        { }

        public override string FileExtension
        {
            get { return "compquery"; }
        }

        protected override IEnumerable<SavedExpression<IExpression>>
            GetAllSavedExpressions(AbstractTypeInferencer inferencer)
        {
            return inferencer.GetAllComparisons();
        }

        protected override void DoExpressionAction(QueryContext<IExpression> context)
        {
            var comp = context.OriginalExpression;
            var compe = comp.AsExpression(context);
            Expression left, right;
            if (compe is MethodCall)
            {
                var args = (compe as MethodCall).Arguments;
                left = args[0];
                right = args[1];
            }
            else if (compe is BinaryOperation)
            {
                var bino = compe as BinaryOperation;
                left = bino.Left;
                right = bino.Right;
            }
            else
            {
                throw new Exception("Unknown comparison expression type: "
                    + comp.GetType());
            }

            if (!left.Type.IsComparableTo(right.Type))
            {
                Logger.PrintLine("Comparison between different types: "
                    + left.Type + " and " + right.Type);
                return;
            }

            Logger.PrintLine("Enclosing type: " + context.ContainingType);
            Logger.PrintLine("Enclosing method: " + context.ContainingMethod);
            Logger.PrintLine("Original Expression: " + compe);
            foreach (var ancestorPair in left.GetAncestors().Select((t, i) => t.PairWith(i))
                                           .CartesianProduct(right.GetAncestors().Select((s, i) => s.PairWith(i))))
            {
                var leftTup = ancestorPair.Item1;
                var rightTup = ancestorPair.Item2;

                var leftCut = leftTup.Item2;
                var rightCut = rightTup.Item2;

                var leftQuery = leftTup.Item1;
                var rightQuery = rightTup.Item1;

                var start = DateTime.Now;
                var depth = 2;
                var results = leftQuery.GetDescendents(context, depth).Where(d => !(d is MethodCall && (d as MethodCall).Method.Name.ToString().Equals("GetHashCode"))).GroupBy(e => e.Type.PairWith(e.BaseCost))
                                         .CartesianProduct(rightQuery.GetDescendents(context, depth).Where(d => !(d is MethodCall && (d as MethodCall).Method.Name.ToString().Equals("GetHashCode"))).GroupBy(e => e.Type.PairWith(e.BaseCost)))
                                .Where(t => t.Item1.Key.Item1.IsComparableTo(t.Item2.Key.Item1))
                                .ToList();

                left.BaseCost = leftCut * 2 * ScoreSwitches.GetMultiplier(ScoreTypes.Depth);
                right.BaseCost = rightCut * 2 * ScoreSwitches.GetMultiplier(ScoreTypes.Depth);

                var resultsTotalCount = results.Sum(g => g.Item1.Count() * g.Item2.Count());
                Logger.PrintLine("Query: " + leftQuery + " < " + rightQuery);
                Logger.Print("Rank: "
                    + ComputeScore(results, context, left.PairWith(right))
                    + " / " + resultsTotalCount
                    + " [left cut=" + leftCut + ", right cut=" + rightCut + "]");
                var end = DateTime.Now;
                Logger.PrintLine("; time: " + (end - start).TotalSeconds);
            }
            Logger.PrintLine();
        }
        protected int ComputeScore(List<Tuple<IGrouping<Tuple<ITypeDefinition, int>,
                                                    Expression>,
                                              IGrouping<Tuple<ITypeDefinition, int>,
                                                    Expression>>> possibleAssigns,
                                   QueryContext<IExpression> context,
                                   Tuple<Expression, Expression> correctAnswer)
        {
            try
            {
                var correctCost = ComputeScore(correctAnswer, context);
                var scoredExps = possibleAssigns
                    //.Where(e => e.Item1.Key.Item2 + e.Item2.Key.Item2 <= correctCost)
                    .SelectMany(e => ComputeScores(e, context))
                    // Never compare something to itself.
                    .Where(t => !t.Item1.Item1.ToString().Equals(t.Item1.Item2.ToString()))
                    .OrderBy(t => t.Item2)
                    .ToList();
                /*
                foreach (var guess in scoredExps.Take(10))
                {
                    Logger.PrintLine("Guess: " + guess);
                }*/
                var badAheadOfCorrect = scoredExps
                        .TakeWhile(t => !t.Item1.Equals(correctAnswer))
                        .Count();
                if (badAheadOfCorrect == scoredExps.Count)
                {
                    return int.MaxValue;
                }
                else
                {
                    return badAheadOfCorrect + 1;
                }
            }
            catch (InvalidOperationException)
            {
                return possibleAssigns.Count + 1;
            }
        }
        protected static IEnumerable<Tuple<Tuple<Expression, Expression>, int>>
                         ComputeScores(Tuple<IGrouping<Tuple<ITypeDefinition, int>,
                                                        Expression>,
                                             IGrouping<Tuple<ITypeDefinition, int>,
                                                        Expression>> assigns,
                                       QueryContext<IExpression> context)
        {
            var leftGroup = assigns.Item1;
            var rightGroup = assigns.Item2;

            var leftKey = leftGroup.Key;
            var rightKey = rightGroup.Key;

            var leftType = leftKey.Item1;
            var rightType = rightKey.Item1;

            var baseCosts = leftKey.Item2 + rightKey.Item2;
            int typeDist = 0;
            if (ScoreSwitches.IsEnabled(ScoreTypes.TypeDistance))
            {
                typeDist = leftType.DistanceFrom(rightType) * ScoreSwitches.GetMultiplier(ScoreTypes.TypeDistance);
            }
            var totalBase = baseCosts + typeDist;

            if (ScoreSwitches.IsEnabled(ScoreTypes.AbstractTypeDistance))
            {
                var leftsWithMeanings = leftGroup
                    .Select(t => t.PairWith(context.GetMeaningsInContext(t))).ToList();
                var rightsWithMeanings = rightGroup
                    .Select(s => s.PairWith(context.GetMeaningsInContext(s))).ToList();
                foreach (var leftTup in leftsWithMeanings)
                {
                    var left = leftTup.Item1;
                    var tMeanings = leftTup.Item2;
                    foreach (var rightTup in rightsWithMeanings)
                    {
                        var right = rightTup.Item1;
                        var sMeanings = rightTup.Item2;
                        var absTypeScore = tMeanings.Overlaps(sMeanings)
                            ? (sbyte)0
                            : ScoreSwitches.GetMultiplier(ScoreTypes.AbstractTypeDistance);

                        var sameNameModifier = ComputeSameNameModifier(left, right);
                        yield return (left.PairWith(right))
                            .PairWith(totalBase + absTypeScore + sameNameModifier);
                    }
                }
            }
            else
            {
                foreach (var left in leftGroup)
                {
                    foreach (var right in rightGroup)
                    {
                        var sameNameModifier = ComputeSameNameModifier(left, right);
                        yield return (left.PairWith(right)).PairWith(totalBase + sameNameModifier);
                    }
                }
            }
        }
        protected static int ComputeSameNameModifier(Expression left, Expression right)
        {
            if (ScoreSwitches.IsEnabled(ScoreTypes.SameName))
            {
                if (left is FieldLookup && right is FieldLookup)
                {
                    if ((left as FieldLookup).FieldName.Equals((right as FieldLookup).FieldName))
                    {
                        return 0;
                    }
                }
                else if (left is PropertyLookup && right is PropertyLookup)
                {
                    if ((left as PropertyLookup).PropertyName.Equals((right as PropertyLookup).PropertyName))
                    {
                        return 0;
                    }
                }
                return ScoreSwitches.GetMultiplier(ScoreTypes.SameName);
            }
            else
            {
                return 0;
            }
        }
        protected static int ComputeScore(Tuple<Expression, Expression> comparison,
            QueryContext<IExpression> context)
        {
            var left = comparison.Item1;
            var right = comparison.Item2;

            return ScoreSwitches.GetMultiplier(ScoreTypes.TypeDistance)
                    * left.Type.DistanceFrom(right.Type)
                + left.BaseCost + right.BaseCost
                + ComputeSameNameModifier(left, right)
                + (ScoreSwitches.IsEnabled(ScoreTypes.AbstractTypeDistance)
                 && context.GetMeaningsInContext(left)
                    .Overlaps(context.GetMeaningsInContext(right))
                   ? (sbyte)0 : ScoreSwitches.GetMultiplier(ScoreTypes.AbstractTypeDistance));
        }
    }
}
